package com.tojabo.sound.ogg;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

import org.vorbis.jcraft.jogg.Packet;
import org.vorbis.jcraft.jogg.Page;
import org.vorbis.jcraft.jogg.StreamState;
import org.vorbis.jcraft.jogg.SyncState;
import org.vorbis.jcraft.jorbis.Block;
import org.vorbis.jcraft.jorbis.Comment;
import org.vorbis.jcraft.jorbis.DspState;
import org.vorbis.jcraft.jorbis.Info;
  

public class OggPlayer {

	private static final long serialVersionUID = 1L;
	private static final Logger LOG = Logger.getLogger(OggPlayer.class
			.getName());
	private static final int Channels = 2;
	private static final int Rate = 11025;//11025 44100
	private final int BUFSIZE = 4096 * 2;
	private int convsize = BUFSIZE * 2; // 2 channels at most
	private byte[] convbuffer = new byte[convsize];
	private SyncState osyncState;
	private StreamState ostreamState;
	private Page opage;
	private Packet opacket;
	private Info vinfo;
	private Comment vcomment;
	private DspState vdspState;
	private Block vblock;
	private byte[] buffer = null;
	private int bytes = 0;
	private SourceDataLine outputLine = null;

	public OggPlayer() {
		
		AudioFormat requestedFormat = new AudioFormat(
				AudioFormat.Encoding.PCM_SIGNED, Rate, 16, Channels, 4, Rate,
				false);		
		try {
			outputLine = AudioSystem.getSourceDataLine(requestedFormat);
			outputLine.open();
		} catch (LineUnavailableException ex) {
			LOG.log(Level.SEVERE, null, ex);
			return;
		}
				
	}

	public static void main(String[] args) {
		OggPlayer player = new OggPlayer();
		String soundfile="E:/tutorial/java_sound/ogg_data/b4.ogg";
		player.play(soundfile);
		
		player.close();
	}
	
	public void play(String soundfile){		
		outputLine.start();

		InputStream in;
		try {			
			in = new FileInputStream(soundfile);
		} catch (FileNotFoundException ex) {
			LOG.log(Level.SEVERE, null, ex);
			return;
		}
		
		startDisplay(in);
		
	}
	
	public void close(){
		outputLine.stop();
		outputLine.close();
	}

	private void init_jorbis() {
		osyncState = new SyncState();
		ostreamState = new StreamState();
		opage = new Page();
		opacket = new Packet();

		vinfo = new Info();
		vcomment = new Comment();
		vdspState = new DspState();
		vblock = new Block(vdspState);

		buffer = null;
		bytes = 0;

		osyncState.init();
	}

	public synchronized void startDisplay(InputStream is) {
		boolean chained = false;
		init_jorbis();

		LOG.log(Level.INFO, "play stream: {0}", is);

		loop: while (true) { // while 1
			int eos = 0;

			int index = osyncState.buffer(BUFSIZE);
			buffer = osyncState.data;
			try {
				bytes = is.read(buffer, index, BUFSIZE);
			} catch (IOException ex) {
				LOG.log(Level.SEVERE, null, ex);
				return;
			}

			osyncState.wrote(bytes);

			if (chained) { //
				chained = false; //
			} //
			else { //
				if (osyncState.pageout(opage) != 1) {
					if (bytes < BUFSIZE) {
						break;
					}
					LOG.info("Input does not appear to be an Ogg bitstream.");
					return;
				}
			} //
			ostreamState.init(opage.serialno());
			ostreamState.reset();

			vinfo.init();
			vcomment.init();

			if (ostreamState.pagein(opage) < 0) {
				// error; stream version mismatch perhaps
				LOG.info("Error reading first page of Ogg bitstream data.");
				return;
			}

			if (ostreamState.packetout(opacket) != 1) {
				// no page? must not be vorbis
				LOG.info("Error reading initial header packet.");
				break;
				// return;
			}

			if (vinfo.synthesis_headerin(vcomment, opacket) < 0) {
				// error case; not a vorbis header
				LOG.info("This Ogg bitstream does not contain Vorbis audio data.");
				return;
			}

			int i = 0;

			while (i < 2) {// while 2
				while (i < 2) {
					int result = osyncState.pageout(opage);
					if (result == 0) {
						break; // Need more data
					}
					if (result == 1) {
						ostreamState.pagein(opage);
						while (i < 2) {
							result = ostreamState.packetout(opacket);
							if (result == 0) {
								break;
							}
							if (result == -1) {
								LOG.info("Corrupt secondary header.  Exiting.");
								// return;
								break loop;
							}
							vinfo.synthesis_headerin(vcomment, opacket);
							i++;
						}
					}
				}

				index = osyncState.buffer(BUFSIZE);
				buffer = osyncState.data;
				try {
					bytes = is.read(buffer, index, BUFSIZE);
				} catch (IOException ex) {
					LOG.log(Level.SEVERE, null, ex);
				}

				if (bytes == 0 && i < 2) {
					LOG.info("End of file before finding all Vorbis headers!");
					return;
				}
				osyncState.wrote(bytes);
			} // end while 2

			{
				byte[][] ptr = vcomment.user_comments;

				for (int j = 0; j < ptr.length; j++) {
					if (ptr[j] == null) {
						break;
					}
					LOG.log(Level.INFO, "Comment: {0}", new String(ptr[j], 0,
							ptr[j].length - 1));
				}
				LOG.log(Level.INFO, "Bitstream is {0} channel, {1}Hz",
						new Object[] { vinfo.channels, vinfo.rate });
				LOG.log(Level.INFO, "Encoded by: {0}\n", new String(
						vcomment.vendor, 0, vcomment.vendor.length - 1));
			}

			convsize = BUFSIZE / vinfo.channels;

			vdspState.synthesis_init(vinfo);
			vblock.init(vdspState);

			float[][][] _pcmf = new float[1][][];
			int[] _index = new int[vinfo.channels];

			// test for rate, channels not 44100, 2
			if (vinfo.channels != Channels || vinfo.rate != Rate) {
				LOG.log(Level.SEVERE,
						"Encountered sound file with unusual channels and rates, should get new line!");
				// return; xxx
			}

			int ttt = 0;
			while (eos == 0) { // while 3
				int xxx = 0;
				while (eos == 0) { // while 3-2
					int result = osyncState.pageout(opage);
					if (result == 0) {
						break; // need more data
					}
					if (result == -1) { // missing or corrupt data at this page
										// position
						LOG.info("Corrupt or missing data in bitstream; continuing...");
					} else {
						ostreamState.pagein(opage);

						if (opage.granulepos() == 0) { //
							chained = true; //
							eos = 1; //
							break; //
						} //

						int sss = 0;
						while (true) { // while 4
							result = ostreamState.packetout(opacket);
							if (result == 0) {
								break; // need more data
							}
							if (result == -1) { // missing or corrupt data at
												// this page position
								// no reason to complain; already complained
								// above
								// System.err.println("no reason to complain; already complained above");
							} else {
								// we have a packet. Decode it
								int samples;
								if (vblock.synthesis(opacket) == 0) { // test
																		// for
									// success!
									vdspState.synthesis_blockin(vblock);
								}
								while ((samples = vdspState.synthesis_pcmout(
										_pcmf, _index)) > 0) { // while 5
									float[][] pcmf = _pcmf[0];
									int bout = (samples < convsize ? samples
											: convsize);

									// convert doubles to 16 bit signed ints
									// (host order) and
									// interleave
									for (i = 0; i < vinfo.channels; i++) {
										int ptr = i * 2;
										// int ptr=i;
										int mono = _index[i];
										for (int j = 0; j < bout; j++) {
											int val = (int) (pcmf[i][mono + j] * 32767.);
											if (val > 32767) {
												val = 32767;
											}
											if (val < -32768) {
												val = -32768;
											}
											if (val < 0) {
												val = val | 0x8000;
											}
											convbuffer[ptr] = (byte) (val);
											convbuffer[ptr + 1] = (byte) (val >>> 8);
											ptr += 2 * (vinfo.channels);
										}
									}
									int framesize = 2 * vinfo.channels * bout;
									System.out.println("framesize:" + framesize
											+ "\tttt:" + (++ttt) + "\txxx: "
											+ (++xxx) + "\tsss: " + (++sss));
									try {
										outputLine.write(convbuffer, 0, 2
												* vinfo.channels * bout);
									} catch (Exception e) {
										// TODO Auto-generated catch block
										// e.printStackTrace();
										System.out.println("xxx wrong"
												+ e.getMessage());
									}
									vdspState.synthesis_read(bout);
								}// end while 5
							}
						}// end while 4
						if (opage.eos() != 0) {
							eos = 1;
						}
					}
				} // end while 3-2

				if (eos == 0) {
					index = osyncState.buffer(BUFSIZE);
					buffer = osyncState.data;
					try {
						bytes = is.read(buffer, index, BUFSIZE);
					} catch (IOException ex) {
						LOG.log(Level.SEVERE, null, ex);
						return;
					}

					if (bytes == -1) {
						break;
					}
					osyncState.wrote(bytes);
					if (bytes == 0) {
						eos = 1;
					}
				}
			}// end while 3

			ostreamState.clear();
			vblock.clear();
			vdspState.clear();
			vinfo.clear();
		} // end while 1

		osyncState.clear();

		if (is != null) {
			try {
				is.close();
			} catch (IOException ex) {
				LOG.log(Level.SEVERE, null, ex);
			}
		}
	}

}
